﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
using OpenPowerPoint.Collection;
using OpenPowerPoint.Conversion;

namespace OpenPowerPoint.Slides.Elements
{
    /// <summary>
    /// Represents a single layoutable shape on a slide
    /// </summary>
    public class Shape : DependencyObject, INotify
    {
        #region Load/Save
        /// <summary>
        /// Loads a shape out of an OpenXML Shape
        /// </summary>
        /// <param name="shape">The OpenXML Shape to load from</param>
        /// <returns>A shape instance according to the OpenXML Shape</returns>
        public static Shape Load(DocumentFormat.OpenXml.Presentation.Shape shape)
        {
            var ret = new Shape();

            if (shape.ShapeProperties.Transform2D != null)
            {
                ret.Rotation = UnitConversion.EmuToDegree(shape.ShapeProperties.Transform2D.Rotation ?? 0);
                ret.Position = new Vector(UnitConversion.EmuToPt(shape.ShapeProperties.Transform2D.Offset.X ?? 0), UnitConversion.EmuToPt(shape.ShapeProperties.Transform2D.Offset.Y ?? 0));
                ret.Size = new Vector(UnitConversion.EmuToPt(shape.ShapeProperties.Transform2D.Extents.Cx ?? 0), UnitConversion.EmuToPt(shape.ShapeProperties.Transform2D.Extents.Cy ?? 0));
            }

            //Load text
            //Get Paragraphs
            List<DocumentFormat.OpenXml.Drawing.Paragraph> paragraphs = shape.TextBody.OfType<DocumentFormat.OpenXml.Drawing.Paragraph>().ToList();

            ret.Content = new FlowDocument();

            foreach (var paragraph in paragraphs)
            {
                Paragraph _par = new Paragraph();

                DocumentFormat.OpenXml.Drawing.ParagraphProperties p_properties =
                    (DocumentFormat.OpenXml.Drawing.ParagraphProperties)paragraph.FirstOrDefault(x => x.GetType() == typeof(DocumentFormat.OpenXml.Drawing.ParagraphProperties));

                if (p_properties != null)
                {
                    if (p_properties.Alignment != null)
                    {
                        switch (p_properties.Alignment.Value)
                        {
                            case DocumentFormat.OpenXml.Drawing.TextAlignmentTypeValues.Left:
                                _par.TextAlignment = TextAlignment.Left;
                                break;
                            case DocumentFormat.OpenXml.Drawing.TextAlignmentTypeValues.Center:
                                _par.TextAlignment = TextAlignment.Center;
                                break;
                            case DocumentFormat.OpenXml.Drawing.TextAlignmentTypeValues.Right:
                                _par.TextAlignment = TextAlignment.Right;
                                break;
                            case DocumentFormat.OpenXml.Drawing.TextAlignmentTypeValues.Justified:
                                _par.TextAlignment = TextAlignment.Justify;
                                break;
                        }
                    }
                }


                List<DocumentFormat.OpenXml.OpenXmlElement> runs = paragraph.ChildElements.ToList();

                foreach (var element in runs)
                {
                    if (element is DocumentFormat.OpenXml.Drawing.Run)
                    {
                        DocumentFormat.OpenXml.Drawing.Run run = element as DocumentFormat.OpenXml.Drawing.Run;

                        Run _run = new Run();

                        DocumentFormat.OpenXml.Drawing.RunProperties r_properties =
                            (DocumentFormat.OpenXml.Drawing.RunProperties)run.FirstOrDefault(x => x.GetType() == typeof(DocumentFormat.OpenXml.Drawing.RunProperties));

                        if (r_properties != null)
                        {
                            if (r_properties.Bold != null)
                                _run.FontWeight = r_properties.Bold ? FontWeights.Bold : FontWeights.Normal;
                            if (r_properties.Italic != null)
                                _run.FontStyle = r_properties.Italic ? FontStyles.Italic : FontStyles.Normal;

                            if (r_properties.Underline != null)
                                _run.TextDecorations = Conversion.DrawingConverters.UnderlineToWpf(r_properties.Underline.Value);

                            _run.Language = XmlLanguage.GetLanguage(r_properties.Language);
                        }

                        _run.Text = run.Text.Text;

                        _par.Inlines.Add(_run);
                    }
                    else if (element is DocumentFormat.OpenXml.Drawing.Break)
                    {
                        var lbreak = new LineBreak();

                        DocumentFormat.OpenXml.Drawing.RunProperties r_properties =
                            (DocumentFormat.OpenXml.Drawing.RunProperties)element.FirstOrDefault(x => x.GetType() == typeof(DocumentFormat.OpenXml.Drawing.RunProperties));

                        if (r_properties != null)
                        {
                            lbreak.Language = XmlLanguage.GetLanguage(r_properties.Language);
                        }

                        _par.Inlines.Add(lbreak);
                    }
                }

                ret.Content.Blocks.Add(_par);
            }

            return ret;
        }
        #endregion

        #region Properties
        FlowDocument _content = new FlowDocument();

        public static DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(FlowDocument), typeof(Shape),
            new PropertyMetadata(default(FlowDocument), (o, e) => ((Shape)o).OnPropertyChanged(e)));

        /// <summary>
        /// The text content of this shape
        /// </summary>
        /// <remarks>FlowDocument does not cast any PropertyChanged event if its content changes, only if the instance changes</remarks>
        public FlowDocument Content
        {
            get { return (FlowDocument)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        public static readonly DependencyProperty RotationProperty = DependencyProperty.Register("Rotation", typeof(double), typeof(Shape),
            new PropertyMetadata(0d, (o, e) => ((Shape)o).OnPropertyChanged(e)));

        /// <summary>
        /// The Rotation of the shape in degree
        /// </summary>
        public double Rotation
        {
            get { return (double)GetValue(RotationProperty); }
            set { SetValue(RotationProperty, value); }
        }

        public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(Vector), typeof(Shape),
            new PropertyMetadata(new Vector(0, 0), (o, e) => ((Shape)o).OnPropertyChanged(e)));

        /// <summary>
        /// The position of the slide in pt
        /// </summary>
        /// <remarks>This value specifies the position of the top left corner of the unrotated (!) slide. To Convert those pt into local units, use the Conversion Namespace</remarks>
        public Vector Position
        {
            get { return (Vector)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }

        public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Vector), typeof(Shape),
            new PropertyMetadata(new Vector(0, 0), (o, e) => ((Shape)o).OnPropertyChanged(e)));

        /// <summary>
        /// The size of the shape in pt
        /// </summary>
        /// <remarks>To Convert those pt into local units, use the Conversion Namespace</remarks>
        public Vector Size
        {
            get { return (Vector)GetValue(SizeProperty); }
            set { SetValue(SizeProperty, value); }
        }
        #endregion

        #region Functions
        /// <summary>
        /// Updates all Colors in the elements
        /// </summary>
        /// <param name="old">The old color</param>
        /// <param name="update"></param>
        internal void UpdateColor(Color old, Color update)
        {
            foreach (var block in Content.Blocks)
            {
                UpdateTextElement(block, old, update);

                if (block is Paragraph)
                {
                    var par = block as Paragraph;

                    foreach (var inline in par.Inlines)
                    {
                        UpdateTextElement(inline, old, update);
                    }
                }

            }
        }

        /// <summary>
        /// Updates the color of any TextElement
        /// </summary>
        /// <param name="element">The element to update</param>
        /// <param name="old">The old color</param>
        /// <param name="update">The updated color</param>
        private void UpdateTextElement(TextElement element, Color old, Color update)
        {
            if (element.Background is SolidColorBrush)
            {
                if (((SolidColorBrush)element.Background).Color == old)
                    element.Background = new SolidColorBrush(update);
            }

            if (element.Foreground is SolidColorBrush)
            {
                if (((SolidColorBrush)element.Foreground).Color == old)
                    element.Foreground = new SolidColorBrush(update);
            }
        }
        #endregion

        #region INotify
        internal event NotifyableList<INotify>.PropertyChangedDelegate PropertyChanged;
        event NotifyableList<INotify>.PropertyChangedDelegate INotify.PropertyChanged
        {
            add { PropertyChanged += value; }
            remove { PropertyChanged -= value; }
        }

        bool _handleChanged = true;

        internal virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs property)
        {
            if (!_handleChanged) return;
            _handleChanged = false;

            var handler = PropertyChanged;
            if (handler != null) handler(this, new SProperty(property.Property, property.OldValue, property.NewValue));

            if (property.Property != null)
                SetValue(property.Property, property.NewValue);

            _handleChanged = true;
        }
        #endregion
    }
}
